import tkinter as tk
from tkinter import ttk, messagebox, filedialog
from pyVim.connect import SmartConnect, Disconnect
from pyVmomi import vim
import ssl
import pandas as pd
from tkinter.font import Font
import json
import os
import threading
import logging
import ctypes
import time

class VCenterInspectorApp:
    def __init__(self, root):
        self.root = root
        self.root.title("vCenter 检查工具")
        
        # 设置固定窗口大小
        self.root.geometry("900x370")
        self.root.resizable(False, False)  # 禁止调整窗口大小
        
        # 设置固定字体
        self.default_font = Font(family="Microsoft YaHei", size=10)
        self.root.option_add("*Font", self.default_font)
        
        # 历史记录文件路径
        self.history_file = os.path.join(os.path.expanduser("~"), ".vcenter_history.json")
        self.load_history()
        
        self.setup_logging()
        
        self.running = False  # 添加运行状态标志
        
        self.create_widgets()
    
    def setup_logging(self):
        log_file = os.path.join(os.path.expanduser("~"), "vcenter_inspector.log")
        logging.basicConfig(
            filename=log_file,
            level=logging.INFO,
            format='%(asctime)s - %(levelname)s - %(message)s'
        )
    
    def load_history(self):
        """加载历史记录"""
        try:
            if os.path.exists(self.history_file):
                with open(self.history_file, 'r', encoding='utf-8') as f:
                    self.history = json.load(f)
            else:
                self.history = {'hosts': [], 'users': []}
        except Exception:
            self.history = {'hosts': [], 'users': []}
    
    def save_history(self):
        """保存历史记录"""
        try:
            with open(self.history_file, 'w', encoding='utf-8') as f:
                json.dump(self.history, f, ensure_ascii=False, indent=2)
        except Exception as e:
            print(f"保存历史记录失败: {e}")
    
    def add_to_history(self, host, user):
        """添加新的历史记录"""
        # 将新记录添加到列表开头
        if host in self.history['hosts']:
            self.history['hosts'].remove(host)
        self.history['hosts'].insert(0, host)
        
        if user in self.history['users']:
            self.history['users'].remove(user)
        self.history['users'].insert(0, user)
        
        # 限制历史记录数量为10个
        self.history['hosts'] = self.history['hosts'][:10]
        self.history['users'] = self.history['users'][:10]
        
        self.save_history()
        
        # 更新下拉菜单
        self.host_combobox['values'] = self.history['hosts']
        self.user_combobox['values'] = self.history['users']
    
    def create_widgets(self):
        # 创建左侧输入区域
        input_frame = ttk.Frame(self.root)
        input_frame.grid(row=0, column=0, padx=10, pady=5, sticky='nsew')
        
        self.host_label = tk.Label(input_frame, text="vCenter 服务器地址:")
        self.host_label.grid(row=0, column=0, padx=10, pady=5, sticky='e')
        self.host_combobox = ttk.Combobox(input_frame, values=self.history['hosts'])
        self.host_combobox.grid(row=0, column=1, padx=10, pady=5, sticky='ew')
        
        self.user_label = tk.Label(input_frame, text="用户名:")
        self.user_label.grid(row=1, column=0, padx=10, pady=5, sticky='e')
        self.user_combobox = ttk.Combobox(input_frame, values=self.history['users'])
        self.user_combobox.grid(row=1, column=1, padx=10, pady=5, sticky='ew')
        
        self.password_label = tk.Label(input_frame, text="密码:")
        self.password_label.grid(row=2, column=0, padx=10, pady=5, sticky='e')
        self.password_entry = tk.Entry(input_frame, show="*")
        self.password_entry.grid(row=2, column=1, padx=10, pady=5, sticky='ew')
        
        self.save_path = tk.StringVar()
        self.path_label = tk.Label(input_frame, text="保存位置:")
        self.path_label.grid(row=3, column=0, padx=10, pady=5, sticky='e')
        self.path_entry = tk.Entry(input_frame, textvariable=self.save_path)
        self.path_entry.grid(row=3, column=1, padx=10, pady=5, sticky='ew')
        self.browse_button = tk.Button(input_frame, text="浏览", command=self.browse_path)
        self.browse_button.grid(row=3, column=2, padx=5, pady=5)
        
        # 添加循环检查时间设置
        self.interval_label = tk.Label(input_frame, text="检查间隔(分钟):")
        self.interval_label.grid(row=4, column=0, padx=10, pady=5, sticky='e')
        self.interval_entry = tk.Entry(input_frame)
        self.interval_entry.insert(0, "5")  # 默认5分钟
        self.interval_entry.grid(row=4, column=1, padx=10, pady=5, sticky='ew')
        
        # 添加循环检查复选框
        self.loop_check_var = tk.BooleanVar()
        self.loop_check = tk.Checkbutton(input_frame, text="循环检查并显示警报", variable=self.loop_check_var)
        self.loop_check.grid(row=4, column=0, columnspan=3, pady=5, sticky='w')

        self.inspect_button = tk.Button(input_frame, text="开始检查", command=self.inspect_vcenter)
        self.inspect_button.grid(row=5, column=0, columnspan=3, pady=10)
        
        self.status_label = tk.Label(input_frame, text="")
        self.status_label.grid(row=6, column=0, columnspan=3, pady=5)
        
        # 创建右侧警报显示区域
        alarm_frame = ttk.LabelFrame(self.root, text="实时警报信息")
        alarm_frame.grid(row=0, column=1, padx=10, pady=5, sticky='nsew')
        
        # 创建警报信息表格
        columns = ('时间', '对象', '类型', '状态', '警报名称')
        self.alarm_tree = ttk.Treeview(alarm_frame, columns=columns, show='headings', height=15)
        
        # 设置列标题
        for col in columns:
            self.alarm_tree.heading(col, text=col)
            self.alarm_tree.column(col, width=100)
        
        # 添加滚动条
        scrollbar = ttk.Scrollbar(alarm_frame, orient='vertical', command=self.alarm_tree.yview)
        self.alarm_tree.configure(yscrollcommand=scrollbar.set)
        
        self.alarm_tree.grid(row=0, column=0, sticky='nsew')
        scrollbar.grid(row=0, column=1, sticky='ns')
        
        # 配置网格权重
        self.root.grid_columnconfigure(1, weight=1)
        alarm_frame.grid_columnconfigure(0, weight=1)
        input_frame.grid_columnconfigure(1, weight=1)
    
    def browse_path(self):
        filename = filedialog.asksaveasfilename(
            defaultextension=".xlsx",
            filetypes=[("Excel files", "*.xlsx")],
            title="选择保存位置"
        )
        if filename:
            self.save_path.set(filename)
    
    def get_power_state_cn(self, power_state):
        """将电源状态转换为中文"""
        power_state_map = {
            'poweredOn': '已开机',
            'poweredOff': '已关机',
            'suspended': '已挂起',
            'unknown': '未知'
        }
        return power_state_map.get(power_state, '未知')

    def get_alarm_status_cn(self, status):
        """将警报状态转换为中文"""
        status_map = {
            'red': '严重',
            'yellow': '警告',
            'green': '正常',
            'gray': '未知'
        }
        return status_map.get(status, status)

    def get_host_status_cn(self, status):
        """将主机状态转换为中文"""
        status_map = {
            'red': '严重',
            'yellow': '警告',
            'green': '正常',
            'gray': '未知'
        }
        return status_map.get(status, status)

    def inspect_vcenter(self):
        # 获取循环时间
        try:
            interval = int(self.interval_entry.get())
            if interval < 1:
                raise ValueError
        except ValueError:
            messagebox.showerror("错误", "请输入有效的检查间隔时间（分钟）")
            return

        # 禁用按钮防止重复点击
        self.inspect_button.config(state='disabled')
        self.status_label.config(text="正在连接...")
        
        # 根据复选框状态决定是否循环检查
        if self.loop_check_var.get():
            # 使用线程执行循环检查
            self.running = True
            threading.Thread(target=self._inspect_vcenter_loop, args=(interval,), daemon=True).start()
        else:
            # 单次检查并保存结果
            self._inspect_vcenter_task(is_loop=False)
            self.inspect_button.config(state='normal')

    def _inspect_vcenter_loop(self, interval):
        while self.running:
            try:
                self._inspect_vcenter_task(is_loop=True)
                # 更新状态标签显示下次检查时间
                next_check = pd.Timestamp.now() + pd.Timedelta(minutes=interval)
                self.status_label.config(text=f"下次检查时间: {next_check.strftime('%Y-%m-%d %H:%M:%S')}")
                # 等待指定的时间间隔
                for _ in range(interval * 60):  # 转换为秒
                    if not self.running:
                        break
                    time.sleep(1)
            except Exception as e:
                logging.error(f"循环检查出错: {str(e)}")
                self.status_label.config(text=f"检查出错: {str(e)}")
                break

    def _inspect_vcenter_task(self, is_loop=False):
        try:
            host = self.host_combobox.get()
            user = self.user_combobox.get()
            password = self.password_entry.get()
            save_path = self.save_path.get()
            
            if not all([host, user, password]):
                if not is_loop:
                    messagebox.showerror("错误", "请填写所有必填项")
                return
            
            try:
                self.status_label.config(text="正在连接 vCenter 服务器...")
                self.root.update()
                
                context = ssl._create_unverified_context()
                si = SmartConnect(host=host, user=user, pwd=password, sslContext=context)
                
                # 连接成功后保存历史记录
                self.add_to_history(host, user)
                
                content = si.RetrieveContent()
                
                # 清空现有警报显示
                for item in self.alarm_tree.get_children():
                    self.alarm_tree.delete(item)

                # 获取警报信息
                alarm_manager = content.alarmManager
                triggered_alarms = []
                
                # 收集主机和虚拟机信息
                host_info_list = []
                vm_info_list = []
                
                # 只遍历主机和虚拟机的警报
                for datacenter in content.rootFolder.childEntity:
                    for cluster in datacenter.hostFolder.childEntity:
                        for host in cluster.host:
                            # 收集主机信息
                            host_info = {
                                '主机名称': host.name,
                                '状态': self.get_power_state_cn(host.runtime.powerState),
                                'CPU使用率(GB)': round(host.summary.quickStats.overallCpuUsage / 1000, 2),
                                '内存使用率(GB)': round(host.summary.quickStats.overallMemoryUsage / 1024, 2),
                                'CPU总数(GB)': round(host.summary.hardware.numCpuCores * host.summary.hardware.cpuMhz / 1000, 2),
                                '内存总数(GB)': round(host.summary.hardware.memorySize / 1024 / 1024 / 1024, 2),
                                'ESXi版本': host.summary.config.product.version,
                                'ESXi构建版本': host.summary.config.product.build
                            }
                            host_info_list.append(host_info)
                            
                            host_alarms = alarm_manager.GetAlarmState(host)
                            for alarm in host_alarms:
                                if alarm.overallStatus not in ['green', 'gray']:
                                    alarm_info = next((a for a in alarm_manager.GetAlarm(host) if a.key == alarm.key), None)
                                    alarm_data = {
                                        '警报时间': alarm.time.strftime("%Y-%m-%d %H:%M:%S") if hasattr(alarm, 'time') else '未知',
                                        '警报对象': host.name,
                                        '对象类型': '主机',
                                        '警报状态': self.get_alarm_status_cn(alarm.overallStatus),
                                        '警报名称': alarm_info.info.name if alarm_info and hasattr(alarm_info.info, 'name') else '未知'
                                    }
                                    triggered_alarms.append(alarm_data)
                                    # 添加到树形视图
                                    self.alarm_tree.insert('', 0, values=(
                                        alarm_data['警报时间'],
                                        alarm_data['警报对象'],
                                        alarm_data['对象类型'],
                                        alarm_data['警报状态'],
                                        alarm_data['警报名称']
                                    ))
                            
                            for vm in host.vm:
                                # 收集虚拟机信息
                                vm_info = {
                                    '虚拟机名称': vm.name,
                                    '状态': self.get_power_state_cn(vm.runtime.powerState),
                                    'CPU数量(核)': vm.config.hardware.numCPU,
                                    '内存大小(GB)': round(vm.config.hardware.memoryMB / 1024, 2),
                                    '操作系统': vm.config.guestFullName,
                                    'IP地址': vm.guest.ipAddress if vm.guest and vm.guest.ipAddress else '未知',
                                    '主机名称': host.name
                                }
                                vm_info_list.append(vm_info)
                                
                                vm_alarms = alarm_manager.GetAlarmState(vm)
                                for alarm in vm_alarms:
                                    if alarm.overallStatus not in ['green', 'gray']:
                                        alarm_info = next((a for a in alarm_manager.GetAlarm(vm) if a.key == alarm.key), None)
                                        alarm_data = {
                                            '警报时间': alarm.time.strftime("%Y-%m-%d %H:%M:%S") if hasattr(alarm, 'time') else '未知',
                                            '警报对象': vm.name,
                                            '对象类型': '虚拟机',
                                            '警报状态': self.get_alarm_status_cn(alarm.overallStatus),
                                            '警报名称': alarm_info.info.name if alarm_info and hasattr(alarm_info.info, 'name') else '未知'
                                        }
                                        triggered_alarms.append(alarm_data)
                                        # 添加到树形视图
                                        self.alarm_tree.insert('', 0, values=(
                                            alarm_data['警报时间'],
                                            alarm_data['警报对象'],
                                            alarm_data['对象类型'],
                                            alarm_data['警报状态'],
                                            alarm_data['警报名称']
                                        ))

            except vim.fault.InvalidLogin:
                if not is_loop:
                    messagebox.showerror("错误", "用户名或密码错误")
            except ssl.SSLError:
                if not is_loop:
                    messagebox.showerror("错误", "SSL证书验证失败")
            except Exception as e:
                if not is_loop:
                    messagebox.showerror("错误", f"发生未知错误: {str(e)}")
            finally:
                if not is_loop and not self.loop_check_var.get():
                    # 只有在非循环模式下才保存数据到Excel
                    if triggered_alarms or host_info_list or vm_info_list:
                        self.status_label.config(text="正在保存数据...")
                        self.root.update()
                        
                        try:
                            with pd.ExcelWriter(save_path, engine='openpyxl') as writer:
                                if triggered_alarms:
                                    df_alarm = pd.DataFrame(triggered_alarms)
                                    df_alarm.to_excel(writer, sheet_name='警报信息', index=False)
                                if host_info_list:
                                    df_host = pd.DataFrame(host_info_list)
                                    df_host.to_excel(writer, sheet_name='主机信息', index=False)
                                if vm_info_list:
                                    df_vm = pd.DataFrame(vm_info_list)
                                    df_vm.to_excel(writer, sheet_name='虚拟机信息', index=False)
                            self.status_label.config(text="检查完成！数据已保存。")
                            messagebox.showinfo("完成", f"数据已保存至：\n{save_path}")
                        except Exception as e:
                            messagebox.showerror("错误", f"保存数据失败: {str(e)}")
                
                if not is_loop:
                    # 只有在非循环模式下才重新启用按钮
                    self.inspect_button.config(state='normal')
                
                Disconnect(si)
        except Exception as e:
            logging.error(f"检查任务出错: {str(e)}")
            self.status_label.config(text=f"检查出错: {str(e)}")

if __name__ == "__main__":
    try:
        # 隐藏控制台窗口
        whnd = ctypes.windll.kernel32.GetConsoleWindow()
        if whnd != 0:
            ctypes.windll.user32.ShowWindow(whnd, 0)
            
        root = tk.Tk()
        app = VCenterInspectorApp(root)
        root.mainloop()
    except Exception as e:
        logging.error(f"程序启动失败: {str(e)}")
        messagebox.showerror("错误", f"程序启动失败: {str(e)}")
    finally:
        # 可选的finally块，用于释放资源等
        pass
